﻿using System.Buffers.Binary;

namespace Basic.Base.System.Extensions.Byte
{
    public class BytesUtils
    {
        private static readonly string WhiteSpace = " ";

        public static readonly ConcurrentDictionary<string, byte[]> MeterSNDict = new ConcurrentDictionary<string, byte[]>();

        public static ushort ToUshort(ReadOnlySpan<byte> bytes, bool isBigEndian = false)
        {
            if (isBigEndian)
                return BinaryPrimitives.ReadUInt16BigEndian(bytes);

            return BinaryPrimitives.ReadUInt16LittleEndian(bytes);
        }

        public static string ToBCDStr(ReadOnlySpan<byte> sourceBytes, bool isBigEndian = false)
        {
            var sb = new StringBuilder();

            if (isBigEndian)
            {
                for (int index = 0; index < sourceBytes.Length; index++)
                    sb.Append(sourceBytes[index].ToString("X2"));
            }
            else
            {
                for (int index = sourceBytes.Length - 1; index >= 0; index--)
                    sb.Append(sourceBytes[index].ToString("X2"));
            }
            return sb.ToString();
        }

        public static byte[] StrToBCD(string bcdStr, bool isBigEndian = false)
        {
            if (MeterSNDict.TryGetValue(bcdStr, out var result))
                return result;

            int len = bcdStr.Length / 2;
            byte[] bytes = new byte[len];

            for (int i = 0; i < len; i++)
                bytes[i] = Convert.ToByte(bcdStr.Substring(i * 2, 2), 16);
            if (isBigEndian == false)
                Array.Reverse(bytes);

            MeterSNDict.TryAdd(bcdStr, bytes);
            return bytes;
        }

        /// <summary>
        /// 事件转字节流(协议定制)
        /// </summary>
        /// <param name="time"></param>
        /// <returns></returns>
        public static byte[] TimeTo7Bytes(DateTime time)
        {
            return new byte[7] { (byte)time.DayOfWeek, (byte)time.Second, (byte)time.Minute, (byte)time.Hour, (byte)time.Day, (byte)time.Month, (byte)(time.Year % 100) };
        }

        /// <summary>
        /// ssmmHHddMMyy
        /// </summary>
        /// <param name="time"></param>
        /// <returns></returns>
        public static byte[] TimeTo6Bytes(DateTime time)
        {
            string str = time.ToString("ssmmHHddMMyy");
            byte[] bytes = new byte[6];
            for (int i = 0; i < bytes.Length; i++)
            {
                bytes[i] = Convert.ToByte(str.Substring(i * 2, 2), 16);
            }
            return bytes;
            //return new byte[6] { (byte)time.Second, (byte)time.Minute, (byte)time.Hour, (byte)time.Day, (byte)time.Month, (byte)(time.Year % 100) };
        }

        public static byte[] DateTo3Bytes(DateTime time)
        {
            string str = time.ToString("HHddMM");
            byte[] bytes = new byte[6];
            for (int i = 0; i < bytes.Length; i++)
            {
                bytes[i] = Convert.ToByte(str.Substring(i * 2, 2), 16);
            }
            return bytes;
            //return new byte[6] { (byte)time.Second, (byte)time.Minute, (byte)time.Hour, (byte)time.Day, (byte)time.Month, (byte)(time.Year % 100) };
        }

        /// <summary>
        /// 检查校验和
        /// </summary>
        /// <param name="rawData">元数据</param>
        /// <param name="checkIndex">校验数据索引位置</param>
        /// <returns></returns>
        public static bool CheckSum(Span<byte> rawData, int checkIndex)
        {
            int len = rawData.Length;
            byte checkSum = 0;
            for (int i = 0; i < len; i++)
            {
                if (i == checkIndex)
                    break;
                checkSum += rawData[i];
            }
            return checkSum == rawData[checkIndex];
        }

        /// <summary>
        /// 设置校验和
        /// </summary>
        /// <param name="rawData"></param>
        /// <param name="checkIndex"></param>
        /// <returns></returns>
        public static byte CalcCheckSum(Span<byte> rawData)
        {
            int len = rawData.Length;
            byte checkSum = 0;
            for (int i = 0; i < len; i++)
            {
                checkSum += rawData[i];
            }
            return checkSum;
        }

        public static string BytesToHexStr(Span<byte> data, bool insertSpace = false)
        {
            StringBuilder builder = new StringBuilder();
            int len = data.Length;
            for (int i = 0; i < len; i++)
            {
                builder.Append(data[i].ToString("X2"));
                if (insertSpace)
                    builder.Append(WhiteSpace);
            }
            return builder.ToString();
        }

        public static byte[] HexStrToBytes(string hexs)
        {
            if (hexs.Contains(WhiteSpace))
                hexs = hexs.Replace(WhiteSpace, string.Empty);

            int len = hexs.Length / 2;
            byte[] data = new byte[len];
            for (int i = 0; i < len; i++)
                data[i] = Convert.ToByte(hexs.Substring(i * 2, 2), 16);

            return data;
        }

        public static byte[] IntTo3Bytes(int number, bool isBigEndian = false)
        {
            int num = 0;
            if (number < 0)
            {
                num = -number;
                num = ~Convert.ToInt32(num);
                num = num + 1;
            }
            else
                num = number;

            Span<byte> bytes = BitConverter.GetBytes(num);
            if (isBigEndian)
                return new byte[3] { bytes[2], bytes[1], bytes[0] };

            return bytes.Slice(0, 3).ToArray();
        }

        public static int ToInt32(Span<byte> data, bool isBigEndian = false)
        {
            if (data.Length == 3)
            {
                if (isBigEndian)
                {
                    if (data[2] >= 0x80)
                        data = new byte[4] { 0xFF, data[0], data[1], data[2], };
                    else
                        data = new byte[4] { 0, data[0], data[1], data[2] };
                }
                else
                {
                    if (data[2] >= 0x80)
                        data = new byte[4] { data[0], data[1], data[2], 0xFF };
                    else
                        data = new byte[4] { data[0], data[1], data[2], 0 };
                }
            }
            if (isBigEndian)
                return BinaryPrimitives.ReadInt32BigEndian(data);

            return BinaryPrimitives.ReadInt32LittleEndian(data);
        }

        public static uint ToUInt32(Span<byte> data, bool isBigEndian = false)
        {
            if (data.Length == 3)
            {
                if (isBigEndian)
                {
                    if (data[2] >= 0x80)
                        data = new byte[4] { data[0], data[1], data[2], 0xFF };
                    else
                        data = new byte[4] { data[0], data[1], data[2], 0 };
                }
                else
                {
                    if (data[2] >= 0x80)
                        data = new byte[4] { 0xFF, data[0], data[1], data[2] };
                    else
                        data = new byte[4] { 0, data[0], data[1], data[2] };
                }
            }
            if (isBigEndian)
                return BinaryPrimitives.ReadUInt32BigEndian(data);

            return BinaryPrimitives.ReadUInt32LittleEndian(data);
        }

    }
}